Chengliang Tang

GU4243/GR5243: Applied Data Science

Pixel Resolution

In digital imaging, the term resolution often refers to the number of pixels in the image.

The convention is to describe pixel resolution with the set of two positive integer numbers, where the first number is the number of pixel columns (width) and the second is the number of pixel rows (height), e.g. 3928 x 6568. Another convention is to cite resolution as the total number of pixels, typically in megapixels, which is the product of pixel columns and pixel rows and dividing by one million, e.g. iPhone Xs Dual 12 MP (megapixel) Camera.

drawing

Figure 1. Example of pixel resolution

Image Downsampling

How to reduce the resolution of an image? Simple - throw away data.

The idea is pretty straightforward: if the original image is too large, we can throw away every other row and every other column (1st, 3rd, 5th, etc) to create a 1/2 size image.

drawing drawing

Figure 2. Example of image downsampling

Image downsampling is the most effective way of dimension reduction, and it sometimes helps reduce noise. However, downsampled images inevitably result in information loss.

Image Super-resolution

In many digital imaging applications, due to the limitation of hardwares, high-resolution images are usually unavailable, yet necessary for finer image processing and analysis.

As an application to image restoration, super-resolution (SR) is a class of techniques that construct high-resolution (HR) images from a single or multiple observed low-resolution (LR) images. Recent development in super-resolution techniques have achieved great success in many areas, such as surveillance video, remote sensing, medical imaging and video standard conversion.

According the number of source images for each target image, super-resolution techniques can be mainly divided into two families: single-frame super-resolution and multi-frame super-resolution. In our project, we are focused on single-frame super-resolution.

drawing

Figure 3. The effects of super-resolution

1. Basic Algorithm: Image Interpolation

Image interpolation, the reverse of downsampling, is the most basic algorithm for super-resolution.

How to increase the resolution of an image? Simple – replicate data. For example, the image is too small, how can we make it 10 times as big? The simplest approach is to repeat each row and column 10 times.

drawing

Figure 4. Image upsampling by repeating row and column

However, merely repeating row and columns won’t recover valuable information, and the upscaled image has rough boundaries.

We can do better!

1.1. A Unified Framework

drawing

Figure 5. Image interpolation

For simplicity, we consider the case of one-dimensional images. A digital image is formed by

\[ F(x, y) = quantize \{f(x, y) \}\] where \(f(x, y)\) is the continuous natural object.

Thus, digital imaging can be described as a discrete point-sampling of a continuous function. And if we could somehow reconstruct the original function, any new image could be generated, at any resolution and scale.

Consider a concrete example for F(x) with the sample values [2, 3, 5, 4, 3, 2, 4], where the gray levels are visualized in height:

drawing

Figure 6. Example of 1-D interpolation

We want to double the size of the image F(x) to create the image G(x)

drawing

Figure 7. Example of 1-D interpolation

Therefore, we have to determine what will be the value of the new pixels.

There are two obvious answers:

  • The first answer consists in doubling each original pixels. This solution is called “replication” to the nearest neighbor because the value is the one of the nearest neighbor giving preference to the right or left neighbor for neighbors at the same distance.

drawing

Figure 8. Example of 1-D interpolation

  • The second answer consists in using the mean value of the nearest known pixels.

drawing

Figure 9. Example of 1-D interpolation

1.2. Interpolation via Convolution

In practice, image interpolation is implemented by convolution. The convolution defines a general principle for the interpolation. The interpolation kernel \(k(i)\) defines the neighborhood to be considered and the weight assigned to them for calculating the value of the central pixel. Mathematically, this corresponds to the operation:

\[ G(x) = \sum_{i}F(x+i) \cdot k(i) \]

The two interpolation methods in 1.1 can both be described by a kernel.

The simplest kernel is the nearest neighbor kernel, which corresponds to a box. It produces images with blocky effect.

drawing

Figure 10. Nearest neighbor kernel

Linear interpolation use a convolution kernel k(x) which has the shape of a triangle. The output image thus has a smoother surface because the discretization is less strong.

drawing

Figure 11. Linear kernel

The most frequently used kernel are B-splines. A B-spline of degree 1 is formed by a series of straight lines, and therefore corresponds to a bilinear kernel. A B-spline of degree 2 is composed of a series of parabolic curves and B-spline of degree 3 is composed of cubic curves.

drawing

Figure 12. B-spline kernel

Here are the 2-dimensional generalization of these kernels

drawing

Figure 13. 2D kernels

1.3. R/Python Examples

In R, we can use resize from package Imager to try different image interpolation kernels

> library(imager)
> img <- load.image('test.jpg')
> ## nearest neighbor interpolation
> im_1 <- resize(img, 500, 500, interpolation_type = 1)
> ## linear interpolation
> im_2 <- resize(img, 500, 500, interpolation_type = 3)
> ## cubic interpolation
> im_3 <- resize(img, 500, 500, interpolation_type = 5)

In Python, similar function is realized by OpenCV

import cv2
img = cv2.imread('test.jpg')
## nearest neighbor
im_1 = cv2.resize(img, (500, 500), interpolation = cv2.INTER_NEAREST)
## linear interpolation
im_2 = cv2.resize(img, (500, 500), interpolation = cv2.INTER_LINEAR)
## cubic interpolation
im_3 = cv2.resize(img, (500, 500), interpolation = cv2.INTER_CUBIC)

drawing

Figure 14. Comparisons of different kernels

2. Baseline Algorithm: Patch-based Method

Can we even do better? Yes!

In the previous section, all the kernels are ‘data-free’, saying their weights are fixed and independent of the images. Yet with predictive modeling, we are able to learn ‘better’ weights from training data.

In this project, the goal is to generate a high-resolution image twice as big for each test image, i.e. number of rows and number of columns are both doubled. The baseline patch-based algorithm is to build low/high-resolution patch pairs, and learn the best mapping between them.

drawing

Figure 15. Baseline method workflow

2.1. Feature Construction

As shown in the above figure, if our goal is to create a twice as big high-resolution version of the original image, it is equivalent to split each pixel into four sub-pixels.

In general, given low and high-resolution image patches \(I_{LR}\) and \(I_{HR}\) with sizes \(D \times D\) and \(U \times U\) (\(D = 3, U = 2\) in the example), respectively, to superresolve the center pixel of \(I_{LR}\) by a factor of \(U\) , we define vectors

\[ x = \text{vectorize}(I_{LR}) - \text{center pixel}(I_{LR}) \in \mathbb{R}^{D^2 \times 1}\] and

\[ y = \text{vectorize}(I_{HR}) - \text{center pixel}(I_{LR}) \in \mathbb{R}^{U^2 \times 1}\]

in a given training set \(\Omega\) of \((x_i, y_i)\) pairs. The task at hand has been transformed into a regression problem.

2.2. Predictive Algorithm

This is a multiple output regression problem and in the literature it is often solved as separate single output regressions. Recently, there has been some work on learning vector valued function, but we adopt the traditional method of treating multiple output regression problems as separate single output regression problems for each output dimension. Therefore, learning the four outputs becomes \(y_j = g_j(x)\) for \(j = 1, ..., 4\), given the input \(x \in \mathbb{R}^{D^2 \times 1}\), and \(g_j\) is estimated by GBM.

2.3. Performance Measure

In image super-resolution, the most used performance measure is Peak Signal-to-noise Ratio, often abbreviated PSNR. It is an engineering term for the ratio between the maximum possible power of a signal and the power of corrupting noise. In practice, PSNR is most easily defined via the mean squared error (MSE). Given a noise-free \(m \times n\) colorful image \(I\) and its noisy approximation \(K\), MSE is defined as:

\[ MSE = \frac{1}{3mn} \sum_{c = 1}^{3}\sum_{i = 1}^{m}\sum_{j=1}^{n}[I(i, j, c) - K(i, j, c)]^2\] Then, PSNR (in dB) is defined as

\[ PSNR = 20 \cdot \text{log}_{10}(MAX_I) - 10 \cdot \text{log}_{10}(MSE)\] where \(MAX_I\) is the maximum possible pixel value (255 or 1) of the image.

3. Advanced Algorithm

In this part, we identify several areas of potential improvement.

3.1. Feature & Predictor

In baseline method, we only use the first outside layer (8 pixels) of each pixel to construct training features, and make predictions using GBM. What about a larger neighborhood and a different predictor?

Also, there is some work on learning vector valued functions, which might alleviate your computational burdens of building twelve predictors.

3.2. Keypoints Detection

In patch-based methods, it is often impractical to create a training feature \(x\) for each pixel. Let’s do a simple calculation. Suppose each image is of the same size 200 x 200, creating a feature for each pixel would result in a feature matrix of 40000 rows, and this number quickly explodes with the increasing size of image set!

According to Shannon’s sampling theorem, for better sampling efficiency, we should have a higher sample rate for high frequency signals, and can have a relatively lower sample rate for high frequency signals. In image analysis, you can think of frequency as the rate of color change. Therefore, we should give more sample weight to areas where color changes drastically, e.g. corners, boundaries.

drawing drawing

Figure 16. Left: raw image; Right: gray-scale image withSIFT keypoints

Image keypoint detection has been a hot research topic in the last 20 years, and there are a bunch of computer vision algorithms for this task, e.g. Laplacian detector, Harris detector, scale-invariant feature transform (SIFT), etc.

3.3. Deep Learning

drawing

Figure 17. Architecture of Super-resolution Convolutional Neural Network (SRCNN)

Image super-resolution with deep learning is one of the most active research areas in computer vision. It has been shown the patch-based algorithm is equivalent to a deep convolutional neural network.

Thus, one area for potential improvement is to learn an end-to-end mapping between the low/high-resolution images. The mapping is represented as a deep convolutional neural network (CNN) that takes the low-resolution image as the input and outputs the high-resolution one. Also, deep learning libraries such as Tensorflow enable you to efficiently build your deep learning architecture in R/Python.

References

  1. Ni, K. S., & Nguyen, T. Q. (2007). Image superresolution using support vector regression. IEEE Transactions on Image Processing, 16(6), 1596-1610. pdf (This paper is the source of our baseline method, where GBM is replaced by SVR.)

  2. Yang, C. Y., Ma, C., & Yang, M. H. (2014, September). Single-image super-resolution: A benchmark. In European Conference on Computer Vision (pp. 372-386). Springer, Cham. pdf (This paper compares different super-resolution algorithms on benchmark set.)

  3. Maalouf, A., & Larabi, M. C. (2013). Image super-resolution, a state-of-the-art review and evaluation. In Advanced Color Image Processing and Analysis (pp. 181-218). Springer, New York, NY. pdf (A review for image super-resolution algorithms.)

  4. Dong, C., Loy, C. C., He, K., & Tang, X. (2016). Image super-resolution using deep convolutional networks. IEEE transactions on pattern analysis and machine intelligence, 38(2), 295-307. pdf, codes (This paper popularized CNN architectures for image super-resolution. Almost all the subsequent state-of-the-art approaches on super-resolution adopted this paradigm.)

Image Source

Figure 1 is from Wikipedia (https://en.wikipedia.org/wiki/File:Resolution_illustration.png)

Figure 2 is from http://www.cs.toronto.edu/~fidler/slides/2015/CSC420/lecture5.pdf

Figure 3 is from https://www.mathworks.com/help/images/single-image-super-resolution-using-deep-learning.html

Figure 6 ~ 12 is from the tutorial by Pantheon Project (https://clouard.users.greyc.fr/Pantheon/experiments/rescaling/index-en.html)

Figure 13 is from Wikipedia (https://upload.wikimedia.org/wikipedia/commons/9/90/Comparison_of_1D_and_2D_interpolation.svg)

Figure 17 is from Image super-resolution using deep convolutional networks (https://arxiv.org/pdf/1501.00092.pdf)

LS0tCnRpdGxlOiAnQWR2YW5jZWQgSW1hZ2UgQW5hbHlzaXM6IFN1cGVyLXJlc29sdXRpb24nCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIHBkZl9kb2N1bWVudDogZGVmYXVsdAotLS0KCkNoZW5nbGlhbmcgVGFuZwoKR1U0MjQzL0dSNTI0MzogQXBwbGllZCBEYXRhIFNjaWVuY2UKCiMjIFBpeGVsIFJlc29sdXRpb24KCkluIGRpZ2l0YWwgaW1hZ2luZywgdGhlIHRlcm0gKnJlc29sdXRpb24qIG9mdGVuIHJlZmVycyB0byB0aGUgbnVtYmVyIG9mIHBpeGVscyBpbiB0aGUgaW1hZ2UuIAoKVGhlIGNvbnZlbnRpb24gaXMgdG8gZGVzY3JpYmUgKnBpeGVsIHJlc29sdXRpb24qIHdpdGggdGhlIHNldCBvZiB0d28gcG9zaXRpdmUgaW50ZWdlciBudW1iZXJzLCB3aGVyZSB0aGUgZmlyc3QgbnVtYmVyIGlzIHRoZSBudW1iZXIgb2YgcGl4ZWwgY29sdW1ucyAod2lkdGgpIGFuZCB0aGUgc2Vjb25kIGlzIHRoZSBudW1iZXIgb2YgcGl4ZWwgcm93cyAoaGVpZ2h0KSwgZS5nLiAzOTI4IHggNjU2OC4gQW5vdGhlciBjb252ZW50aW9uIGlzIHRvIGNpdGUgcmVzb2x1dGlvbiBhcyB0aGUgdG90YWwgbnVtYmVyIG9mIHBpeGVscywgdHlwaWNhbGx5IGluIG1lZ2FwaXhlbHMsIHdoaWNoIGlzIHRoZSBwcm9kdWN0IG9mIHBpeGVsIGNvbHVtbnMgYW5kIHBpeGVsIHJvd3MgYW5kIGRpdmlkaW5nIGJ5IG9uZSBtaWxsaW9uLCBlLmcuIGlQaG9uZSBYcyBEdWFsIDEyIE1QIChtZWdhcGl4ZWwpIENhbWVyYS4KCjxjZW50ZXI+Cgo8aW1nIHNyYz0iLi9kZW1vX2ltZy9SZXNvbHV0aW9uX2lsbHVzdHJhdGlvbi5wbmciIGFsdD0iZHJhd2luZyIgd2lkdGg9IjgwMCIvPgoKRmlndXJlIDEuIEV4YW1wbGUgb2YgcGl4ZWwgcmVzb2x1dGlvbgoKPC9jZW50ZXI+CgoKIyMgSW1hZ2UgRG93bnNhbXBsaW5nCgpIb3cgdG8gcmVkdWNlIHRoZSByZXNvbHV0aW9uIG9mIGFuIGltYWdlPyBTaW1wbGUgLSB0aHJvdyBhd2F5IGRhdGEuCgpUaGUgaWRlYSBpcyBwcmV0dHkgc3RyYWlnaHRmb3J3YXJkOiBpZiB0aGUgb3JpZ2luYWwgaW1hZ2UgaXMgdG9vIGxhcmdlLCB3ZSBjYW4gdGhyb3cgYXdheSBldmVyeSBvdGhlciByb3cgYW5kIGV2ZXJ5IG90aGVyIGNvbHVtbiAoMXN0LCAzcmQsIDV0aCwgZXRjKSB0byBjcmVhdGUgYSAxLzIgc2l6ZSBpbWFnZS4KCjxjZW50ZXI+Cgo8aW1nIHNyYz0iLi9kZW1vX2ltZy9WRy5wbmciIGFsdD0iZHJhd2luZyIgd2lkdGg9IjQwMCIvPgo8aW1nIHNyYz0iLi9kZW1vX2ltZy9WRzIucG5nIiBhbHQ9ImRyYXdpbmciIHdpZHRoPSI0MDAiLz4KCkZpZ3VyZSAyLiBFeGFtcGxlIG9mIGltYWdlIGRvd25zYW1wbGluZwoKPC9jZW50ZXI+CgoKSW1hZ2UgZG93bnNhbXBsaW5nIGlzIHRoZSBtb3N0IGVmZmVjdGl2ZSB3YXkgb2YgZGltZW5zaW9uIHJlZHVjdGlvbiwgYW5kIGl0IHNvbWV0aW1lcyBoZWxwcyByZWR1Y2Ugbm9pc2UuIEhvd2V2ZXIsIGRvd25zYW1wbGVkIGltYWdlcyBpbmV2aXRhYmx5IHJlc3VsdCBpbiBpbmZvcm1hdGlvbiBsb3NzLgoKIyMgSW1hZ2UgU3VwZXItcmVzb2x1dGlvbgpJbiBtYW55IGRpZ2l0YWwgaW1hZ2luZyBhcHBsaWNhdGlvbnMsIGR1ZSB0byB0aGUgbGltaXRhdGlvbiBvZiBoYXJkd2FyZXMsIGhpZ2gtcmVzb2x1dGlvbiBpbWFnZXMgYXJlIHVzdWFsbHkgdW5hdmFpbGFibGUsIHlldCBuZWNlc3NhcnkgZm9yIGZpbmVyIGltYWdlIHByb2Nlc3NpbmcgYW5kIGFuYWx5c2lzLiAKCkFzIGFuIGFwcGxpY2F0aW9uIHRvIGltYWdlIHJlc3RvcmF0aW9uLCAqc3VwZXItcmVzb2x1dGlvbiogKFNSKSBpcyBhIGNsYXNzIG9mIHRlY2huaXF1ZXMgdGhhdCBjb25zdHJ1Y3QgaGlnaC1yZXNvbHV0aW9uIChIUikgaW1hZ2VzIGZyb20gYSBzaW5nbGUgb3IgbXVsdGlwbGUgb2JzZXJ2ZWQgbG93LXJlc29sdXRpb24gKExSKSBpbWFnZXMuIFJlY2VudCBkZXZlbG9wbWVudCBpbiBzdXBlci1yZXNvbHV0aW9uIHRlY2huaXF1ZXMgaGF2ZSBhY2hpZXZlZCBncmVhdCBzdWNjZXNzIGluIG1hbnkgYXJlYXMsIHN1Y2ggYXMgc3VydmVpbGxhbmNlIHZpZGVvLCByZW1vdGUgc2Vuc2luZywgbWVkaWNhbCBpbWFnaW5nIGFuZCB2aWRlbyBzdGFuZGFyZCBjb252ZXJzaW9uLgoKQWNjb3JkaW5nIHRoZSBudW1iZXIgb2Ygc291cmNlIGltYWdlcyBmb3IgZWFjaCB0YXJnZXQgaW1hZ2UsIHN1cGVyLXJlc29sdXRpb24gdGVjaG5pcXVlcyBjYW4gYmUgbWFpbmx5IGRpdmlkZWQgaW50byB0d28gZmFtaWxpZXM6ICpzaW5nbGUtZnJhbWUgc3VwZXItcmVzb2x1dGlvbiogYW5kICptdWx0aS1mcmFtZSBzdXBlci1yZXNvbHV0aW9uKi4gSW4gb3VyIHByb2plY3QsIHdlIGFyZSBmb2N1c2VkIG9uIHNpbmdsZS1mcmFtZSBzdXBlci1yZXNvbHV0aW9uLgoKPGNlbnRlcj4KCjxpbWcgc3JjPSIuL2RlbW9faW1nL3N1cGVyX3Jlc29sdXRpb24ucG5nIiBhbHQ9ImRyYXdpbmciIHdpZHRoPSI3MDAiLz4KCkZpZ3VyZSAzLiBUaGUgZWZmZWN0cyBvZiBzdXBlci1yZXNvbHV0aW9uCgo8L2NlbnRlcj4KCiMjIyAxLiBCYXNpYyBBbGdvcml0aG06IEltYWdlIEludGVycG9sYXRpb24KCkltYWdlIGludGVycG9sYXRpb24sIHRoZSByZXZlcnNlIG9mIGRvd25zYW1wbGluZywgaXMgdGhlIG1vc3QgYmFzaWMgYWxnb3JpdGhtIGZvciBzdXBlci1yZXNvbHV0aW9uLiAKCkhvdyB0byBpbmNyZWFzZSB0aGUgcmVzb2x1dGlvbiBvZiBhbiBpbWFnZT8gU2ltcGxlIOKAkyByZXBsaWNhdGUgZGF0YS4gRm9yIGV4YW1wbGUsIHRoZSBpbWFnZSBpcyB0b28gc21hbGwsIGhvdyBjYW4gd2UgbWFrZSBpdCAxMCB0aW1lcyBhcyBiaWc/IFRoZSBzaW1wbGVzdCBhcHByb2FjaCBpcyB0byByZXBlYXQgZWFjaCByb3cgYW5kIGNvbHVtbiAxMCB0aW1lcy4KCjxjZW50ZXI+Cgo8aW1nIHNyYz0iLi9kZW1vX2ltZy90ZW4ucG5nIiBhbHQ9ImRyYXdpbmciIHdpZHRoPSI2MDAiLz4KCkZpZ3VyZSA0LiBJbWFnZSB1cHNhbXBsaW5nIGJ5IHJlcGVhdGluZyByb3cgYW5kIGNvbHVtbgoKPC9jZW50ZXI+CgpIb3dldmVyLCBtZXJlbHkgcmVwZWF0aW5nIHJvdyBhbmQgY29sdW1ucyB3b24ndCByZWNvdmVyIHZhbHVhYmxlIGluZm9ybWF0aW9uLCBhbmQgdGhlIHVwc2NhbGVkIGltYWdlIGhhcyByb3VnaCBib3VuZGFyaWVzLiAKCldlIGNhbiBkbyBiZXR0ZXIhCgojIyMjIDEuMS4gQSBVbmlmaWVkIEZyYW1ld29yawo8Y2VudGVyPgoKPGltZyBzcmM9Ii4vZGVtb19pbWcvdHdvLWludGVycG9sYXRpb24ucG5nIiBhbHQ9ImRyYXdpbmciIHdpZHRoPSI1MDAiLz4KCkZpZ3VyZSA1LiBJbWFnZSBpbnRlcnBvbGF0aW9uCgo8L2NlbnRlcj4KCgpGb3Igc2ltcGxpY2l0eSwgd2UgY29uc2lkZXIgdGhlIGNhc2Ugb2Ygb25lLWRpbWVuc2lvbmFsIGltYWdlcy4gQSBkaWdpdGFsIGltYWdlIGlzIGZvcm1lZCBieQoKJCQgRih4LCB5KSA9IHF1YW50aXplIFx7Zih4LCB5KSBcfSQkCndoZXJlICRmKHgsIHkpJCBpcyB0aGUgY29udGludW91cyBuYXR1cmFsIG9iamVjdC4KClRodXMsIGRpZ2l0YWwgaW1hZ2luZyBjYW4gYmUgZGVzY3JpYmVkIGFzIGEgZGlzY3JldGUgcG9pbnQtc2FtcGxpbmcgb2YgYSBjb250aW51b3VzIGZ1bmN0aW9uLiBBbmQgaWYgd2UgY291bGQgc29tZWhvdyByZWNvbnN0cnVjdCB0aGUgb3JpZ2luYWwgZnVuY3Rpb24sIGFueSBuZXcgaW1hZ2UgY291bGQgYmUgZ2VuZXJhdGVkLCBhdCBhbnkgcmVzb2x1dGlvbiBhbmQgc2NhbGUuCgpDb25zaWRlciBhIGNvbmNyZXRlIGV4YW1wbGUgZm9yIEYoeCkgd2l0aCB0aGUgc2FtcGxlIHZhbHVlcyBbMiwgMywgNSwgNCwgMywgMiwgNF0sIHdoZXJlIHRoZSBncmF5IGxldmVscyBhcmUgdmlzdWFsaXplZCBpbiBoZWlnaHQ6Cgo8Y2VudGVyPgoKPGltZyBzcmM9Ii4vZGVtb19pbWcvZXhwLTEuZ2lmIiBhbHQ9ImRyYXdpbmciIHdpZHRoPSIxNTAiLz4KCkZpZ3VyZSA2LiBFeGFtcGxlIG9mIDEtRCBpbnRlcnBvbGF0aW9uCgo8L2NlbnRlcj4KCldlIHdhbnQgdG8gZG91YmxlIHRoZSBzaXplIG9mIHRoZSBpbWFnZSBGKHgpIHRvIGNyZWF0ZSB0aGUgaW1hZ2UgRyh4KSAKCjxjZW50ZXI+Cgo8aW1nIHNyYz0iLi9kZW1vX2ltZy9leHAtMi5naWYiIGFsdD0iZHJhd2luZyIgd2lkdGg9IjMwMCIvPgoKRmlndXJlIDcuIEV4YW1wbGUgb2YgMS1EIGludGVycG9sYXRpb24KCjwvY2VudGVyPgoKVGhlcmVmb3JlLCB3ZSBoYXZlIHRvIGRldGVybWluZSB3aGF0IHdpbGwgYmUgdGhlIHZhbHVlIG9mIHRoZSBuZXcgcGl4ZWxzLgoKVGhlcmUgYXJlIHR3byBvYnZpb3VzIGFuc3dlcnM6CgoqIFRoZSBmaXJzdCBhbnN3ZXIgY29uc2lzdHMgaW4gZG91YmxpbmcgZWFjaCBvcmlnaW5hbCBwaXhlbHMuIFRoaXMgc29sdXRpb24gaXMgY2FsbGVkICJyZXBsaWNhdGlvbiIgdG8gdGhlIG5lYXJlc3QgbmVpZ2hib3IgYmVjYXVzZSB0aGUgdmFsdWUgaXMgdGhlIG9uZSBvZiB0aGUgbmVhcmVzdCBuZWlnaGJvciBnaXZpbmcgcHJlZmVyZW5jZSB0byB0aGUgcmlnaHQgb3IgbGVmdCBuZWlnaGJvciBmb3IgbmVpZ2hib3JzIGF0IHRoZSBzYW1lIGRpc3RhbmNlLiAKCjxjZW50ZXI+Cgo8aW1nIHNyYz0iLi9kZW1vX2ltZy9leHAtMy5naWYiIGFsdD0iZHJhd2luZyIgd2lkdGg9IjMwMCIvPgoKRmlndXJlIDguIEV4YW1wbGUgb2YgMS1EIGludGVycG9sYXRpb24KCjwvY2VudGVyPgoKKiBUaGUgc2Vjb25kIGFuc3dlciBjb25zaXN0cyBpbiB1c2luZyB0aGUgbWVhbiB2YWx1ZSBvZiB0aGUgbmVhcmVzdCBrbm93biBwaXhlbHMuIAoKPGNlbnRlcj4KCjxpbWcgc3JjPSIuL2RlbW9faW1nL2V4cC00LmdpZiIgYWx0PSJkcmF3aW5nIiB3aWR0aD0iMzAwIi8+CgpGaWd1cmUgOS4gRXhhbXBsZSBvZiAxLUQgaW50ZXJwb2xhdGlvbgoKPC9jZW50ZXI+CgoKCiMjIyMgMS4yLiBJbnRlcnBvbGF0aW9uIHZpYSBDb252b2x1dGlvbgoKSW4gcHJhY3RpY2UsIGltYWdlIGludGVycG9sYXRpb24gaXMgaW1wbGVtZW50ZWQgYnkgY29udm9sdXRpb24uIFRoZSBjb252b2x1dGlvbiBkZWZpbmVzIGEgZ2VuZXJhbCBwcmluY2lwbGUgZm9yIHRoZSBpbnRlcnBvbGF0aW9uLiBUaGUgaW50ZXJwb2xhdGlvbiBrZXJuZWwgJGsoaSkkIGRlZmluZXMgdGhlIG5laWdoYm9yaG9vZCB0byBiZSBjb25zaWRlcmVkIGFuZCB0aGUgd2VpZ2h0IGFzc2lnbmVkIHRvIHRoZW0gZm9yIGNhbGN1bGF0aW5nIHRoZSB2YWx1ZSBvZiB0aGUgY2VudHJhbCBwaXhlbC4gTWF0aGVtYXRpY2FsbHksIHRoaXMgY29ycmVzcG9uZHMgdG8gdGhlIG9wZXJhdGlvbjoKCiQkIEcoeCkgPSBcc3VtX3tpfUYoeCtpKSBcY2RvdCBrKGkpICQkCgpUaGUgdHdvIGludGVycG9sYXRpb24gbWV0aG9kcyBpbiAxLjEgY2FuIGJvdGggYmUgZGVzY3JpYmVkIGJ5IGEga2VybmVsLgoKVGhlIHNpbXBsZXN0IGtlcm5lbCBpcyB0aGUgbmVhcmVzdCBuZWlnaGJvciBrZXJuZWwsIHdoaWNoIGNvcnJlc3BvbmRzIHRvIGEgYm94LiBJdCBwcm9kdWNlcyBpbWFnZXMgd2l0aCBibG9ja3kgZWZmZWN0LgoKPGNlbnRlcj4KCjxpbWcgc3JjPSIuL2RlbW9faW1nL2tlcm4tMS5wbmciIGFsdD0iZHJhd2luZyIgd2lkdGg9IjYwMCIvPgoKRmlndXJlIDEwLiBOZWFyZXN0IG5laWdoYm9yIGtlcm5lbAoKPC9jZW50ZXI+CgpMaW5lYXIgaW50ZXJwb2xhdGlvbiB1c2UgYSBjb252b2x1dGlvbiBrZXJuZWwgayh4KSB3aGljaCBoYXMgdGhlIHNoYXBlIG9mIGEgdHJpYW5nbGUuIFRoZSBvdXRwdXQgaW1hZ2UgdGh1cyBoYXMgYSBzbW9vdGhlciBzdXJmYWNlIGJlY2F1c2UgdGhlIGRpc2NyZXRpemF0aW9uIGlzIGxlc3Mgc3Ryb25nLgoKPGNlbnRlcj4KCjxpbWcgc3JjPSIuL2RlbW9faW1nL2tlcm4tMi5wbmciIGFsdD0iZHJhd2luZyIgd2lkdGg9IjYwMCIvPgoKRmlndXJlIDExLiBMaW5lYXIga2VybmVsCgo8L2NlbnRlcj4KClRoZSBtb3N0IGZyZXF1ZW50bHkgdXNlZCBrZXJuZWwgYXJlIEItc3BsaW5lcy4gQSBCLXNwbGluZSBvZiBkZWdyZWUgMSBpcyBmb3JtZWQgYnkgYSBzZXJpZXMgb2Ygc3RyYWlnaHQgbGluZXMsIGFuZCB0aGVyZWZvcmUgY29ycmVzcG9uZHMgdG8gYSBiaWxpbmVhciBrZXJuZWwuIEEgQi1zcGxpbmUgb2YgZGVncmVlIDIgaXMgY29tcG9zZWQgb2YgYSBzZXJpZXMgb2YgcGFyYWJvbGljIGN1cnZlcyBhbmQgQi1zcGxpbmUgb2YgZGVncmVlIDMgaXMgY29tcG9zZWQgb2YgY3ViaWMgY3VydmVzLgoKPGNlbnRlcj4KCjxpbWcgc3JjPSIuL2RlbW9faW1nL2tlcm4tMy5wbmciIGFsdD0iZHJhd2luZyIgd2lkdGg9IjYwMCIvPgoKRmlndXJlIDEyLiBCLXNwbGluZSBrZXJuZWwKCjwvY2VudGVyPgoKSGVyZSBhcmUgdGhlIDItZGltZW5zaW9uYWwgZ2VuZXJhbGl6YXRpb24gb2YgdGhlc2Uga2VybmVscwoKPGNlbnRlcj4KCjxpbWcgc3JjPSIuL2RlbW9faW1nL2Jpa2VybmVscy5wbmciIGFsdD0iZHJhd2luZyIgd2lkdGg9IjUwMCIvPgoKRmlndXJlIDEzLiAyRCBrZXJuZWxzCgo8L2NlbnRlcj4KCgoKIyMjIyAxLjMuIFIvUHl0aG9uIEV4YW1wbGVzCgpJbiBSLCB3ZSBjYW4gdXNlIGByZXNpemVgIGZyb20gcGFja2FnZSBgSW1hZ2VyYCB0byB0cnkgZGlmZmVyZW50IGltYWdlIGludGVycG9sYXRpb24ga2VybmVscwoKYGBgCj4gbGlicmFyeShpbWFnZXIpCj4gaW1nIDwtIGxvYWQuaW1hZ2UoJ3Rlc3QuanBnJykKPiAjIyBuZWFyZXN0IG5laWdoYm9yIGludGVycG9sYXRpb24KPiBpbV8xIDwtIHJlc2l6ZShpbWcsIDUwMCwgNTAwLCBpbnRlcnBvbGF0aW9uX3R5cGUgPSAxKQo+ICMjIGxpbmVhciBpbnRlcnBvbGF0aW9uCj4gaW1fMiA8LSByZXNpemUoaW1nLCA1MDAsIDUwMCwgaW50ZXJwb2xhdGlvbl90eXBlID0gMykKPiAjIyBjdWJpYyBpbnRlcnBvbGF0aW9uCj4gaW1fMyA8LSByZXNpemUoaW1nLCA1MDAsIDUwMCwgaW50ZXJwb2xhdGlvbl90eXBlID0gNSkKYGBgCgpJbiBQeXRob24sIHNpbWlsYXIgZnVuY3Rpb24gaXMgcmVhbGl6ZWQgYnkgT3BlbkNWCgpgYGAKaW1wb3J0IGN2MgppbWcgPSBjdjIuaW1yZWFkKCd0ZXN0LmpwZycpCiMjIG5lYXJlc3QgbmVpZ2hib3IKaW1fMSA9IGN2Mi5yZXNpemUoaW1nLCAoNTAwLCA1MDApLCBpbnRlcnBvbGF0aW9uID0gY3YyLklOVEVSX05FQVJFU1QpCiMjIGxpbmVhciBpbnRlcnBvbGF0aW9uCmltXzIgPSBjdjIucmVzaXplKGltZywgKDUwMCwgNTAwKSwgaW50ZXJwb2xhdGlvbiA9IGN2Mi5JTlRFUl9MSU5FQVIpCiMjIGN1YmljIGludGVycG9sYXRpb24KaW1fMyA9IGN2Mi5yZXNpemUoaW1nLCAoNTAwLCA1MDApLCBpbnRlcnBvbGF0aW9uID0gY3YyLklOVEVSX0NVQklDKQpgYGAKPGNlbnRlcj4KCjxpbWcgc3JjPSIuL2RlbW9faW1nL2ludGVycG9sYXRpb24ucG5nIiBhbHQ9ImRyYXdpbmciIHdpZHRoPSI2MDAiLz4KCkZpZ3VyZSAxNC4gQ29tcGFyaXNvbnMgb2YgZGlmZmVyZW50IGtlcm5lbHMKCjwvY2VudGVyPgoKIyMjIDIuIEJhc2VsaW5lIEFsZ29yaXRobTogUGF0Y2gtYmFzZWQgTWV0aG9kCgpDYW4gd2UgZXZlbiBkbyBiZXR0ZXI/IFllcyEKCkluIHRoZSBwcmV2aW91cyBzZWN0aW9uLCBhbGwgdGhlIGtlcm5lbHMgYXJlICdkYXRhLWZyZWUnLCBzYXlpbmcgdGhlaXIgd2VpZ2h0cyBhcmUgZml4ZWQgYW5kIGluZGVwZW5kZW50IG9mIHRoZSBpbWFnZXMuIFlldCB3aXRoIHByZWRpY3RpdmUgbW9kZWxpbmcsIHdlIGFyZSBhYmxlIHRvIGxlYXJuICdiZXR0ZXInIHdlaWdodHMgZnJvbSB0cmFpbmluZyBkYXRhLiAKCkluIHRoaXMgcHJvamVjdCwgdGhlIGdvYWwgaXMgdG8gZ2VuZXJhdGUgYSBoaWdoLXJlc29sdXRpb24gaW1hZ2UgdHdpY2UgYXMgYmlnIGZvciBlYWNoIHRlc3QgaW1hZ2UsIGkuZS4gbnVtYmVyIG9mIHJvd3MgYW5kIG51bWJlciBvZiBjb2x1bW5zIGFyZSBib3RoIGRvdWJsZWQuIFRoZSBiYXNlbGluZSBwYXRjaC1iYXNlZCBhbGdvcml0aG0gaXMgdG8gYnVpbGQgbG93L2hpZ2gtcmVzb2x1dGlvbiBwYXRjaCBwYWlycywgYW5kIGxlYXJuIHRoZSBiZXN0IG1hcHBpbmcgYmV0d2VlbiB0aGVtLgoKCjxjZW50ZXI+Cgo8aW1nIHNyYz0iLi9kZW1vX2ltZy9iYXNlbGluZS5wbmciIGFsdD0iZHJhd2luZyIgd2lkdGg9IjYwMCIvPgoKRmlndXJlIDE1LiBCYXNlbGluZSBtZXRob2Qgd29ya2Zsb3cKCjwvY2VudGVyPgoKCiMjIyMgMi4xLiBGZWF0dXJlIENvbnN0cnVjdGlvbgoKQXMgc2hvd24gaW4gdGhlIGFib3ZlIGZpZ3VyZSwgaWYgb3VyIGdvYWwgaXMgdG8gY3JlYXRlIGEgdHdpY2UgYXMgYmlnIGhpZ2gtcmVzb2x1dGlvbiB2ZXJzaW9uIG9mIHRoZSBvcmlnaW5hbCBpbWFnZSwgaXQgaXMgZXF1aXZhbGVudCB0byBzcGxpdCBlYWNoIHBpeGVsIGludG8gZm91ciBzdWItcGl4ZWxzLgoKSW4gZ2VuZXJhbCwgZ2l2ZW4gbG93IGFuZCBoaWdoLXJlc29sdXRpb24gaW1hZ2UgcGF0Y2hlcyAkSV97TFJ9JCBhbmQgJElfe0hSfSQgd2l0aCBzaXplcyAkRCBcdGltZXMgRCQgYW5kICRVIFx0aW1lcyBVJCAoJEQgPSAzLCBVID0gMiQgaW4gdGhlIGV4YW1wbGUpLCByZXNwZWN0aXZlbHksIHRvIHN1cGVycmVzb2x2ZSB0aGUgY2VudGVyIHBpeGVsIG9mICRJX3tMUn0kIGJ5IGEgZmFjdG9yIG9mICRVJCAsIHdlIGRlZmluZSB2ZWN0b3JzCgokJCB4ID0gXHRleHR7dmVjdG9yaXplfShJX3tMUn0pIC0gXHRleHR7Y2VudGVyIHBpeGVsfShJX3tMUn0pIFxpbiBcbWF0aGJie1J9XntEXjIgXHRpbWVzIDF9JCQKYW5kCgokJCB5ID0gXHRleHR7dmVjdG9yaXplfShJX3tIUn0pIC0gXHRleHR7Y2VudGVyIHBpeGVsfShJX3tMUn0pIFxpbiBcbWF0aGJie1J9XntVXjIgXHRpbWVzIDF9JCQKCmluIGEgZ2l2ZW4gdHJhaW5pbmcgc2V0ICRcT21lZ2EkIG9mICQoeF9pLCB5X2kpJCBwYWlycy4gVGhlIHRhc2sgYXQgaGFuZCBoYXMgYmVlbiB0cmFuc2Zvcm1lZCBpbnRvIGEgcmVncmVzc2lvbiBwcm9ibGVtLgoKCiMjIyMgMi4yLiBQcmVkaWN0aXZlIEFsZ29yaXRobQoKVGhpcyBpcyBhIG11bHRpcGxlIG91dHB1dCByZWdyZXNzaW9uIHByb2JsZW0gYW5kIGluIHRoZSBsaXRlcmF0dXJlIGl0IGlzIG9mdGVuIHNvbHZlZCBhcyBzZXBhcmF0ZSBzaW5nbGUgb3V0cHV0IHJlZ3Jlc3Npb25zLiBSZWNlbnRseSwgdGhlcmUgaGFzIGJlZW4gc29tZSB3b3JrIG9uIGxlYXJuaW5nIHZlY3RvciB2YWx1ZWQgZnVuY3Rpb24sIGJ1dCB3ZSBhZG9wdCB0aGUgdHJhZGl0aW9uYWwgbWV0aG9kIG9mIHRyZWF0aW5nIG11bHRpcGxlIG91dHB1dCByZWdyZXNzaW9uIHByb2JsZW1zIGFzIHNlcGFyYXRlIHNpbmdsZSBvdXRwdXQgcmVncmVzc2lvbiBwcm9ibGVtcyBmb3IgZWFjaCBvdXRwdXQgZGltZW5zaW9uLiBUaGVyZWZvcmUsIGxlYXJuaW5nIHRoZSBmb3VyIG91dHB1dHMgYmVjb21lcyAkeV9qID0gZ19qKHgpJCBmb3IgJGogPSAxLCAuLi4sIDQkLCBnaXZlbiB0aGUgaW5wdXQgJHggXGluIFxtYXRoYmJ7Un1ee0ReMiBcdGltZXMgMX0kLCBhbmQgJGdfaiQgaXMgZXN0aW1hdGVkIGJ5IEdCTS4KCiMjIyMgMi4zLiBQZXJmb3JtYW5jZSBNZWFzdXJlCkluIGltYWdlIHN1cGVyLXJlc29sdXRpb24sIHRoZSBtb3N0IHVzZWQgcGVyZm9ybWFuY2UgbWVhc3VyZSBpcyAqUGVhayBTaWduYWwtdG8tbm9pc2UgUmF0aW8qLCBvZnRlbiBhYmJyZXZpYXRlZCBQU05SLiBJdCBpcyBhbiBlbmdpbmVlcmluZyB0ZXJtIGZvciB0aGUgcmF0aW8gYmV0d2VlbiB0aGUgbWF4aW11bSBwb3NzaWJsZSBwb3dlciBvZiBhIHNpZ25hbCBhbmQgdGhlIHBvd2VyIG9mIGNvcnJ1cHRpbmcgbm9pc2UuIEluIHByYWN0aWNlLCBQU05SIGlzIG1vc3QgZWFzaWx5IGRlZmluZWQgdmlhIHRoZSBtZWFuIHNxdWFyZWQgZXJyb3IgKE1TRSkuIEdpdmVuIGEgbm9pc2UtZnJlZSAkbSBcdGltZXMgbiQgY29sb3JmdWwgaW1hZ2UgJEkkIGFuZCBpdHMgbm9pc3kgYXBwcm94aW1hdGlvbiAkSyQsIE1TRSBpcyBkZWZpbmVkIGFzOgoKJCQgTVNFID0gXGZyYWN7MX17M21ufSBcc3VtX3tjID0gMX1eezN9XHN1bV97aSA9IDF9XnttfVxzdW1fe2o9MX1ee259W0koaSwgaiwgYykgLSBLKGksIGosIGMpXV4yJCQKVGhlbiwgUFNOUiAoaW4gZEIpIGlzIGRlZmluZWQgYXMKCiQkIFBTTlIgPSAyMCBcY2RvdCBcdGV4dHtsb2d9X3sxMH0oTUFYX0kpIC0gMTAgXGNkb3QgXHRleHR7bG9nfV97MTB9KE1TRSkkJAp3aGVyZSAkTUFYX0kkIGlzIHRoZSBtYXhpbXVtIHBvc3NpYmxlIHBpeGVsIHZhbHVlICgyNTUgb3IgMSkgb2YgdGhlIGltYWdlLgoKIyMjIDMuIEFkdmFuY2VkIEFsZ29yaXRobQpJbiB0aGlzIHBhcnQsIHdlIGlkZW50aWZ5IHNldmVyYWwgYXJlYXMgb2YgcG90ZW50aWFsIGltcHJvdmVtZW50LgoKIyMjIyAzLjEuIEZlYXR1cmUgJiBQcmVkaWN0b3IKSW4gYmFzZWxpbmUgbWV0aG9kLCB3ZSBvbmx5IHVzZSB0aGUgZmlyc3Qgb3V0c2lkZSBsYXllciAoOCBwaXhlbHMpIG9mIGVhY2ggcGl4ZWwgdG8gY29uc3RydWN0IHRyYWluaW5nIGZlYXR1cmVzLCBhbmQgbWFrZSBwcmVkaWN0aW9ucyB1c2luZyBHQk0uIFdoYXQgYWJvdXQgYSBsYXJnZXIgbmVpZ2hib3Job29kIGFuZCBhIGRpZmZlcmVudCBwcmVkaWN0b3I/IAoKQWxzbywgdGhlcmUgaXMgc29tZSB3b3JrIG9uIGxlYXJuaW5nIHZlY3RvciB2YWx1ZWQgZnVuY3Rpb25zLCB3aGljaCBtaWdodCBhbGxldmlhdGUgeW91ciBjb21wdXRhdGlvbmFsIGJ1cmRlbnMgb2YgYnVpbGRpbmcgdHdlbHZlIHByZWRpY3RvcnMuCgoKIyMjIyAzLjIuIEtleXBvaW50cyBEZXRlY3Rpb24KSW4gcGF0Y2gtYmFzZWQgbWV0aG9kcywgaXQgaXMgb2Z0ZW4gaW1wcmFjdGljYWwgdG8gY3JlYXRlIGEgdHJhaW5pbmcgZmVhdHVyZSAkeCQgZm9yIGVhY2ggcGl4ZWwuIExldCdzIGRvIGEgc2ltcGxlIGNhbGN1bGF0aW9uLiBTdXBwb3NlIGVhY2ggaW1hZ2UgaXMgb2YgdGhlIHNhbWUgc2l6ZSAyMDAgeCAyMDAsIGNyZWF0aW5nIGEgZmVhdHVyZSBmb3IgZWFjaCBwaXhlbCB3b3VsZCByZXN1bHQgaW4gYSBmZWF0dXJlIG1hdHJpeCBvZiA0MDAwMCByb3dzLCBhbmQgdGhpcyBudW1iZXIgcXVpY2tseSBleHBsb2RlcyB3aXRoIHRoZSBpbmNyZWFzaW5nIHNpemUgb2YgaW1hZ2Ugc2V0IQoKQWNjb3JkaW5nIHRvIFNoYW5ub24ncyBzYW1wbGluZyB0aGVvcmVtLCBmb3IgYmV0dGVyIHNhbXBsaW5nIGVmZmljaWVuY3ksIHdlIHNob3VsZCBoYXZlIGEgaGlnaGVyIHNhbXBsZSByYXRlIGZvciBoaWdoIGZyZXF1ZW5jeSBzaWduYWxzLCBhbmQgY2FuIGhhdmUgYSByZWxhdGl2ZWx5IGxvd2VyIHNhbXBsZSByYXRlIGZvciBoaWdoIGZyZXF1ZW5jeSBzaWduYWxzLiBJbiBpbWFnZSBhbmFseXNpcywgeW91IGNhbiB0aGluayBvZiBmcmVxdWVuY3kgYXMgdGhlIHJhdGUgb2YgY29sb3IgY2hhbmdlLiBUaGVyZWZvcmUsIHdlIHNob3VsZCBnaXZlIG1vcmUgc2FtcGxlIHdlaWdodCB0byBhcmVhcyB3aGVyZSBjb2xvciBjaGFuZ2VzIGRyYXN0aWNhbGx5LCBlLmcuIGNvcm5lcnMsIGJvdW5kYXJpZXMuCgo8Y2VudGVyPgoKPGltZyBzcmM9Ii4vZGVtb19pbWcvY2F0LmpwZyIgYWx0PSJkcmF3aW5nIiB3aWR0aD0iMzAwIi8+CjxpbWcgc3JjPSIuL2RlbW9faW1nL3NpZnRfa2V5cG9pbnRzLmpwZyIgYWx0PSJkcmF3aW5nIiB3aWR0aD0iMzAwIi8+CgpGaWd1cmUgMTYuIExlZnQ6IHJhdyBpbWFnZTsgUmlnaHQ6IGdyYXktc2NhbGUgaW1hZ2Ugd2l0aFNJRlQga2V5cG9pbnRzCgo8L2NlbnRlcj4KCkltYWdlIGtleXBvaW50IGRldGVjdGlvbiBoYXMgYmVlbiBhIGhvdCByZXNlYXJjaCB0b3BpYyBpbiB0aGUgbGFzdCAyMCB5ZWFycywgYW5kIHRoZXJlIGFyZSBhIGJ1bmNoIG9mIGNvbXB1dGVyIHZpc2lvbiBhbGdvcml0aG1zIGZvciB0aGlzIHRhc2ssIGUuZy4gTGFwbGFjaWFuIGRldGVjdG9yLCBIYXJyaXMgZGV0ZWN0b3IsIHNjYWxlLWludmFyaWFudCBmZWF0dXJlIHRyYW5zZm9ybSAoU0lGVCksIGV0Yy4KCiMjIyMgMy4zLiBEZWVwIExlYXJuaW5nCjxjZW50ZXI+Cgo8aW1nIHNyYz0iLi9kZW1vX2ltZy9kZWVwLWxlYXJuaW5nLnBuZyIgYWx0PSJkcmF3aW5nIiB3aWR0aD0iODAwIi8+CgpGaWd1cmUgMTcuIEFyY2hpdGVjdHVyZSBvZiBTdXBlci1yZXNvbHV0aW9uIENvbnZvbHV0aW9uYWwgTmV1cmFsIE5ldHdvcmsgKFNSQ05OKQoKPC9jZW50ZXI+CgpJbWFnZSBzdXBlci1yZXNvbHV0aW9uIHdpdGggZGVlcCBsZWFybmluZyBpcyBvbmUgb2YgdGhlIG1vc3QgYWN0aXZlIHJlc2VhcmNoIGFyZWFzIGluIGNvbXB1dGVyIHZpc2lvbi4gSXQgaGFzIGJlZW4gc2hvd24gdGhlIHBhdGNoLWJhc2VkIGFsZ29yaXRobSBpcyBlcXVpdmFsZW50IHRvIGEgZGVlcCBjb252b2x1dGlvbmFsIG5ldXJhbCBuZXR3b3JrLiAKClRodXMsIG9uZSBhcmVhIGZvciBwb3RlbnRpYWwgaW1wcm92ZW1lbnQgaXMgdG8gbGVhcm4gYW4gZW5kLXRvLWVuZCBtYXBwaW5nIGJldHdlZW4gdGhlIGxvdy9oaWdoLXJlc29sdXRpb24gaW1hZ2VzLiBUaGUgbWFwcGluZyBpcyByZXByZXNlbnRlZCBhcyBhIGRlZXAgY29udm9sdXRpb25hbCBuZXVyYWwgbmV0d29yayAoQ05OKSB0aGF0IHRha2VzIHRoZSBsb3ctcmVzb2x1dGlvbiBpbWFnZSBhcyB0aGUgaW5wdXQgYW5kIG91dHB1dHMgdGhlIGhpZ2gtcmVzb2x1dGlvbiBvbmUuIEFsc28sIGRlZXAgbGVhcm5pbmcgbGlicmFyaWVzIHN1Y2ggYXMgYFRlbnNvcmZsb3dgIGVuYWJsZSB5b3UgdG8gZWZmaWNpZW50bHkgYnVpbGQgeW91ciBkZWVwIGxlYXJuaW5nIGFyY2hpdGVjdHVyZSBpbiBSL1B5dGhvbi4KCiMjIFJlZmVyZW5jZXMgCgoxLiBOaSwgSy4gUy4sICYgTmd1eWVuLCBULiBRLiAoMjAwNykuIEltYWdlIHN1cGVycmVzb2x1dGlvbiB1c2luZyBzdXBwb3J0IHZlY3RvciByZWdyZXNzaW9uLiAqSUVFRSBUcmFuc2FjdGlvbnMgb24gSW1hZ2UgUHJvY2Vzc2luZyosIDE2KDYpLCAxNTk2LTE2MTAuIFtwZGZdKGh0dHA6Ly9jaXRlc2VlcnguaXN0LnBzdS5lZHUvdmlld2RvYy9kb3dubG9hZD9kb2k9MTAuMS4xLjY0OS45MzQxJnJlcD1yZXAxJnR5cGU9cGRmKQooVGhpcyBwYXBlciBpcyB0aGUgc291cmNlIG9mIG91ciBiYXNlbGluZSBtZXRob2QsIHdoZXJlIEdCTSBpcyByZXBsYWNlZCBieSBTVlIuKQoKMi4gWWFuZywgQy4gWS4sIE1hLCBDLiwgJiBZYW5nLCBNLiBILiAoMjAxNCwgU2VwdGVtYmVyKS4gU2luZ2xlLWltYWdlIHN1cGVyLXJlc29sdXRpb246IEEgYmVuY2htYXJrLiAqSW4gRXVyb3BlYW4gQ29uZmVyZW5jZSBvbiBDb21wdXRlciBWaXNpb24qIChwcC4gMzcyLTM4NikuIFNwcmluZ2VyLCBDaGFtLiBbcGRmXShodHRwczovL3d3dy5yZXNlYXJjaGdhdGUubmV0L3Byb2ZpbGUvTWluZy1Ic3Vhbl9ZYW5nMi9wdWJsaWNhdGlvbi8yNzg2OTM4MjRfU2luZ2xlLUltYWdlX1N1cGVyLVJlc29sdXRpb25fQV9CZW5jaG1hcmsvbGlua3MvNTZmNDZiZTcwOGFlMzhkNzEwOWY2Yjg1L1NpbmdsZS1JbWFnZS1TdXBlci1SZXNvbHV0aW9uLUEtQmVuY2htYXJrLnBkZikgKFRoaXMgcGFwZXIgY29tcGFyZXMgZGlmZmVyZW50IHN1cGVyLXJlc29sdXRpb24gYWxnb3JpdGhtcyBvbiBiZW5jaG1hcmsgc2V0LikKCjMuIE1hYWxvdWYsIEEuLCAmIExhcmFiaSwgTS4gQy4gKDIwMTMpLiBJbWFnZSBzdXBlci1yZXNvbHV0aW9uLCBhIHN0YXRlLW9mLXRoZS1hcnQgcmV2aWV3IGFuZCBldmFsdWF0aW9uLiAqSW4gQWR2YW5jZWQgQ29sb3IgSW1hZ2UgUHJvY2Vzc2luZyBhbmQgQW5hbHlzaXMqIChwcC4gMTgxLTIxOCkuIFNwcmluZ2VyLCBOZXcgWW9yaywgTlkuIFtwZGZdKGh0dHBzOi8vbGluay5zcHJpbmdlci5jb20vY29udGVudC9wZGYvMTAuMTAwNyUyRjk3OC0xLTQ0MTktNjE5MC03XzcucGRmKSAoQSByZXZpZXcgZm9yIGltYWdlIHN1cGVyLXJlc29sdXRpb24gYWxnb3JpdGhtcy4pCgozLiBEb25nLCBDLiwgTG95LCBDLiBDLiwgSGUsIEsuLCAmIFRhbmcsIFguICgyMDE2KS4gSW1hZ2Ugc3VwZXItcmVzb2x1dGlvbiB1c2luZyBkZWVwIGNvbnZvbHV0aW9uYWwgbmV0d29ya3MuICpJRUVFIHRyYW5zYWN0aW9ucyBvbiBwYXR0ZXJuIGFuYWx5c2lzIGFuZCBtYWNoaW5lIGludGVsbGlnZW5jZSosIDM4KDIpLCAyOTUtMzA3LgpbcGRmXShodHRwczovL2FyeGl2Lm9yZy9wZGYvMTUwMS4wMDA5Mi5wZGYpLCBbY29kZXNdKGh0dHA6Ly9tbWxhYi5pZS5jdWhrLmVkdS5oay9wcm9qZWN0cy9TUkNOTi5odG1sKSAoVGhpcyBwYXBlciBwb3B1bGFyaXplZCBDTk4gYXJjaGl0ZWN0dXJlcyBmb3IgaW1hZ2Ugc3VwZXItcmVzb2x1dGlvbi4gQWxtb3N0IGFsbCB0aGUgc3Vic2VxdWVudCBzdGF0ZS1vZi10aGUtYXJ0IGFwcHJvYWNoZXMgb24gc3VwZXItcmVzb2x1dGlvbiBhZG9wdGVkIHRoaXMgcGFyYWRpZ20uKQoKCiMjIEltYWdlIFNvdXJjZQoKRmlndXJlIDEgaXMgZnJvbSBXaWtpcGVkaWEgKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0ZpbGU6UmVzb2x1dGlvbl9pbGx1c3RyYXRpb24ucG5nKQoKRmlndXJlIDIgaXMgZnJvbSBodHRwOi8vd3d3LmNzLnRvcm9udG8uZWR1L35maWRsZXIvc2xpZGVzLzIwMTUvQ1NDNDIwL2xlY3R1cmU1LnBkZgoKRmlndXJlIDMgaXMgZnJvbSBodHRwczovL3d3dy5tYXRod29ya3MuY29tL2hlbHAvaW1hZ2VzL3NpbmdsZS1pbWFnZS1zdXBlci1yZXNvbHV0aW9uLXVzaW5nLWRlZXAtbGVhcm5pbmcuaHRtbAoKRmlndXJlIDYgfiAxMiBpcyBmcm9tIHRoZSB0dXRvcmlhbCBieSBQYW50aGVvbiBQcm9qZWN0IChodHRwczovL2Nsb3VhcmQudXNlcnMuZ3JleWMuZnIvUGFudGhlb24vZXhwZXJpbWVudHMvcmVzY2FsaW5nL2luZGV4LWVuLmh0bWwpCgpGaWd1cmUgMTMgaXMgZnJvbSBXaWtpcGVkaWEgKGh0dHBzOi8vdXBsb2FkLndpa2ltZWRpYS5vcmcvd2lraXBlZGlhL2NvbW1vbnMvOS85MC9Db21wYXJpc29uX29mXzFEX2FuZF8yRF9pbnRlcnBvbGF0aW9uLnN2ZykKCkZpZ3VyZSAxNyBpcyBmcm9tICpJbWFnZSBzdXBlci1yZXNvbHV0aW9uIHVzaW5nIGRlZXAgY29udm9sdXRpb25hbCBuZXR3b3JrcyogKGh0dHBzOi8vYXJ4aXYub3JnL3BkZi8xNTAxLjAwMDkyLnBkZikKCgoKCgoKCgoK